package com.gitee.huanminabc.jcommon.base;

import com.gitee.huanminabc.jcommon.system.MachineCodeGenerator;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.function.LongSupplier;

/**
 * 单例模式的雪花ID生成器
 * 支持机器码自动、手动设置；高并发安全；更易维护
 * 论上最大支持 1024 台物理/虚拟机同时生成雪花ID且不会冲突。
 * 只要每台机器的机器编号（machineId）在 0 ~ 1023 之间，并且不会重复分配，就能保证生成的ID在分布式环境下唯一且不冲突。
 */
@Slf4j
public class SnowIdUtil {

    // ====== 雪花算法参数常量 ======
    private static final long SEQUENCE_BITS = 12L;     // 序列号位数
    private static final long MACHINE_BITS = 10L;      // 机器ID位数
    private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_BITS); // 1023
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);  // 4095
    private static final long TIMESTAMP_LEFT = SEQUENCE_BITS + MACHINE_BITS; // 左移位数
    public static final long DEFAULT_START_TIMESTAMP = 1557489395327L; // 可自定义

    // ====== 单例实现 ======
    private static class Holder {
        private static final SnowflakeIdWorker INSTANCE = new SnowflakeIdWorker();
    }

    /**
     * 雪花ID生成核心实现
     */
    private static class SnowflakeIdWorker {
        // 可自定义的起始时间戳
        private final long startTimestamp;
        // 机器ID部分（低10位）
        private final long machineIdPart;
        // 序列号和lastStamp为实例变量，避免线程间干扰
        private long sequence = 0L;
        private long lastStamp = -1L;

        // 可自定义时间/机器码实现
        private final LongSupplier timeGen;

        // 默认构造：自动获取机器码
        public SnowflakeIdWorker() {
            this(DEFAULT_START_TIMESTAMP, resolveMachineId(), System::currentTimeMillis);
        }

        // 支持自定义时间基点/机器码/时间生成器
        public SnowflakeIdWorker(long startTimestamp, long machineId, LongSupplier timeGen) {
            this.startTimestamp = startTimestamp;
            this.machineIdPart = (machineId & MAX_MACHINE_ID) << SEQUENCE_BITS;
            this.timeGen = timeGen;
        }

        // 雪花ID生成算法
        public synchronized long nextId() {
            long currentStamp = timeGen.getAsLong();

            // 处理时钟回拨
            if (currentStamp < lastStamp) {
                long offset = lastStamp - currentStamp;
                // 容忍回拨1ms，否则抛异常
                if (offset <= 1) {
                    try { TimeUnit.MILLISECONDS.sleep(offset); }
                    catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                    currentStamp = timeGen.getAsLong();
                } else {
                    throw new RuntimeException(
                            String.format("时钟回拨，拒绝生成id，回拨毫秒数: %d", offset)
                    );
                }
            }

            if (currentStamp == lastStamp) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                // 序列号溢出，自旋到下一个毫秒
                if (sequence == 0) {
                    currentStamp = waitUntilNextMillis(lastStamp);
                }
            } else {
                sequence = 0L;
            }
            lastStamp = currentStamp;
            return ((currentStamp - startTimestamp) << TIMESTAMP_LEFT)
                    | machineIdPart | sequence;
        }

        // 等到下一个毫秒
        private long waitUntilNextMillis(long lastMillis) {
            long now = timeGen.getAsLong();
            while (now <= lastMillis) {
                now = timeGen.getAsLong();
            }
            return now;
        }

        // 获取/回退机器码
        private static long resolveMachineId() {
            try {
                return MachineCodeGenerator.getMachineCode();
            } catch (Exception e) {
                // fallback: 随机或默认
                long randomId = java.util.concurrent.ThreadLocalRandom.current().nextLong(0, MAX_MACHINE_ID + 1);
                log.error("获取机器码失败，使用随机ID: " + randomId);
                return randomId;
            }
        }
    }

    // 对外接口：获取long型雪花ID
    public static long uniqueLong() {
        return Holder.INSTANCE.nextId();
    }

    // 对外接口：16进制字符串ID（补零至16位）
    public static String uniqueLongHex() {
        return String.format("%016x", uniqueLong());
    }

    // 支持外部自定义机器码和起始时间戳（适用于分布式手动分配）
    public static SnowflakeIdWorker customWorker(long startTimestamp, long machineId) {
        return new SnowflakeIdWorker(startTimestamp, machineId, System::currentTimeMillis);
    }

    // 分片辅助方法
    public static long slicePosition(long snowId, int zoneSize) {
        if (zoneSize <= 0) return 0L;
        return Math.floorMod(snowId, zoneSize);
    }
}